﻿
using Red.Core;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Red.EngineData.View
{
    /// <summary>
    ///     Defines a class or struct serializable form the Interop.
    /// </summary>
    [AttributeUsage( AttributeTargets.Struct | AttributeTargets.Class )]
    public class InteropTypeAttribute : MappableAttribute
    {
        private static readonly Dictionary< string, Type > _interopTypeMap = new Dictionary<string, Type>();
        private static bool _rebuildMapScheduled = true;

        public string TypeName { get; set; }

        public static Dictionary<string, Type> TypeMap
        {
            get
            {
                if ( _rebuildMapScheduled )
                {
                    RebuildTypeMap();
                    _rebuildMapScheduled = false;
                }
                return _interopTypeMap;
            }
        }

        public InteropTypeAttribute( string interopTypeName )
        {
            _rebuildMapScheduled = true;
            TypeName = interopTypeName;
        }

        private static void RebuildTypeMap()
        {
            _interopTypeMap.Clear();
            ClassMap.RegisterClassesWithAtrributeListener<InteropTypeAttribute>(
                ( Type type, InteropTypeAttribute attr ) =>
                {
                    _interopTypeMap.Add( attr.TypeName, type );
                } );
        }
    }

    /// <summary>
    ///     Defines an attribute serializable from the Interop
    /// </summary>
    [AttributeUsage( AttributeTargets.Property )]
    public class InteropPropertyAttribute : Attribute
    {
        private string _interopName;

        public string InteropName
        {
            get { return _interopName; }
            set { _interopName = value; }
        }

        public InteropPropertyAttribute( string interopName )
        {
            _interopName = interopName;
        }
    }

    /// <summary>
    ///     Static class containing methods for serializing objects from Interop data
    /// </summary>
    public static class InteropSerializer
    {
        #region Public methods
        
        /// <summary>
        ///     Deserialize object from Interop data
        /// </summary>
        /// <typeparam name="T">
        ///     Type of object to deserialize
        /// </typeparam>
        /// <param name="view">
        ///     Interop view representing the object
        /// </param>
        /// <exception cref="InvalidCastException"></exception>
        /// <exception cref="InteropException"></exception>
        /// <returns>
        ///     Deserialized object
        /// </returns>
        public async static Task<T> Deserialize<T>( DataView view ) where T : new()
        {
            var res = await Deserialize( view, typeof( T ) );
            return (T)res;
        }

        /// <summary>
        ///     Deserialize object from Interop data
        /// </summary>
        /// <typeparam name="T">
        ///     Type of object to deserialize
        /// </typeparam>
        /// <param name="view">
        ///     Interop view representing the object
        /// </param>
        /// <exception cref="InvalidCastException"></exception>
        /// <exception cref="InteropException"></exception>
        /// <returns>
        ///     Deserialized object
        /// </returns>
        public static Task<object> Deserialize( DataView view, Type type )
        {
            var tcs = new TaskCompletionSource<object>();

            view.Refresh( async ex =>
                {
                    if ( ex == null )
                    {
                        if ( view.IsArray )
                        {
                            await DeserializeArray( view, type, tcs );
                        }
                        else if ( view.IsCompound )
                        {
                            await DeserializeCompound( view, type, tcs );
                        }
                        else
                        {
                            await DeserializeSimple( view, type, tcs );
                        }
                    }
                    else
                        throw ex;
                } );

            return tcs.Task;
        }
        
        #endregion Public methods

        #region Private methods
        
        private static Task<object> DeserializeSimple( DataView view, Type type, TaskCompletionSource<object> tcs )
        {
            try
            {
                object val = Convert.ChangeType( view.Value, type );
                tcs.SetResult( val );
            }
            catch ( System.InvalidCastException ex )
            {
                tcs.SetException( ex );
            }

            return tcs.Task;
        }

        private static async Task<object> DeserializeCompound( DataView view, Type type, TaskCompletionSource<object> tcs )
        {
            // This adds support for polymorphic properties - the destination type (on the c# side) may be just base class
            Type actualType = NegotiateType( type, view.Type );

            object obj = Activator.CreateInstance( actualType );

            PropertyInfo[] props = actualType.GetProperties();
            foreach ( PropertyInfo prop in props )
            {
                object[] attrs = prop.GetCustomAttributes( true );
                foreach ( object attr in attrs )
                {
                    InteropPropertyAttribute interopAttr = attr as InteropPropertyAttribute;
                    if ( interopAttr != null )
                    {
                        DataView propView = view.GetSubView( interopAttr.InteropName );
                        object propVal = await Deserialize( propView, prop.PropertyType );
                        prop.SetValue( obj, propVal );
                        break;
                    }
                }
            }

            tcs.SetResult( obj );

            return tcs.Task;
        }

        private static Task<object> DeserializeMap( DataView view, Type type, TaskCompletionSource<object> tcs )
        {
            tcs.SetException( new NotImplementedException() );
            return tcs.Task;
        }

        private static async Task<object> DeserializeArray( DataView view, Type type, TaskCompletionSource<object> tcs )
        {
            object obj = Activator.CreateInstance( type );
            var list = obj as IList;

            if ( list != null )
            {
                foreach ( string elemName in view.Children )
                {
                    DataView elemView = view.GetSubView( elemName );
                    Type elemType = list.AsQueryable().ElementType;
                    object elemVal = await Deserialize( elemView, elemType );
                    list.Add( elemVal );
                }

                tcs.SetResult( obj );
            }
            else
            {
                tcs.SetException( new Exception( "type mismatch" ) );
            }

            return tcs.Task;
        }

        private static Type NegotiateType( Type clrType, string interopType )
        {
            if ( !String.IsNullOrEmpty( interopType ) )
            {
                Type registeredType;
                if ( InteropTypeAttribute.TypeMap.TryGetValue( interopType, out registeredType ) )
                {
                    return registeredType;
                }
            }

            return clrType; // fall back to target type if no interopType given
        }
        
        #endregion Private methods

    }
}